package com.efrei.uchat.server;

import java.io.IOException;
import java.net.Socket;
import java.util.List;
import java.util.Observable;
import java.util.Observer;

import org.apache.log4j.Logger;

import sun.misc.Lock;

import com.efrei.uchat.exception.UChatException;
import com.efrei.uchat.io.Session;
import com.efrei.uchat.io.Sessions;
import com.efrei.uchat.message.CroomUsersMessage;
import com.efrei.uchat.message.Message;
import com.efrei.uchat.model.croom.Croom;
import com.efrei.uchat.model.croom.Crooms;
import com.efrei.uchat.server.strategies.MessageStrategy;
import com.efrei.uchat.utils.Tools;

public class UChatServer implements Observer, Runnable {
	
	private static final Logger logger = Logger.getLogger(UChatServer.class);
	
	private Server server;
	
	private Sessions sessions;
	private Lock sessionsLock;
	
	private Crooms crooms;
	private Lock croomsLock;
	
	public UChatServer() {
		super();
		sessions = new Sessions(this);
		sessionsLock = new Lock();
		crooms = new Crooms();
		croomsLock = new Lock();
		
		// start socket server
		server = new Server();
		server.addObserver(this);
		new Thread(server, "Listening-Server").start();
	}

	@Override
	public void update(Observable o, Object arg) {
		if(o instanceof Server){
			Socket socket = (Socket)arg;
			try {
				sessions.newClient(socket);
			} catch (UChatException e) {
				e.printStackTrace();
			} catch (IOException e) {
				e.printStackTrace();
			}
		} else if(o instanceof ClientListener) {
			try {
				manageMessage((Message)arg);
			} catch(InterruptedException e) {
				// sessionsLock problem
				e.printStackTrace();
			} catch (UChatException e) {
				// logic problem
				e.printStackTrace();
			}
		}
		
	}

	private void manageMessage(Message message) throws InterruptedException, UChatException {
		logger.debug("manage message "+message.toString());
		
		Class<MessageStrategy> strategy =
				MessageStrategies.getMessageStrategy(message.getType());
		if(strategy==null) {
			logger.error("Strategy unassociated with "+message.getType());
			return;
		}
		
		// execute worker
		List<Message> messages = null;
		try {
			MessageStrategy worker = strategy.getConstructor().newInstance();
			messages = worker.manage(message, this);
		} catch(UChatException e) {
			logger.error("an exception occured", e);
			return;
		} catch (Exception e) {
			logger.error("an exception occured", e);
			return;
		}

		// send response
		if(messages==null) {
			return;
		}
		sessionsLock.lock();
		try {
			Session session;
			for (Message msg : messages) {
				session = sessions.getSession(msg.getSessionId());
				if(session != null) {
					logger.info("Send message "+msg+
							" to "+session.getChatter().getPseudo());
					session.send(msg);
				} else {
					logger.error("Invalid sessionId for this generated message: "+msg);
				}
			}
		} finally {
			sessionsLock.unlock();
		}
	}
	
	public void sessionDisconnected(Session session) {
		try {
			getSessionsLock().lock();
			getCroomsLock().lock();
		} catch (InterruptedException e1) {
			e1.printStackTrace();
			disconnect();
		}
		try {
			// for all user's crooms
			logger.info("Manage "+session.getChatter()+" deconnection, notify all other users");
			for (Croom croom : getCrooms().disconnect(session)) {
				// remove user
				croom.removeSession(session);
				// send list users to all users
				CroomUsersMessage message;
				for (Session ss : croom.getSessions()) {
					// create message
					message = new CroomUsersMessage(ss.getId(), croom.getTopic());
					for (Session ss2 : croom.getSessions()) {
						message.addSession(ss2);
					}
					logger.debug(String.format("notify %s about croom %s", ss.getChatter(), croom));
					ss.send(message);
				}
			}
			getSessions().removeSession(session.getId());
		} catch(Exception e) {
			e.printStackTrace();
		} finally {
			getCroomsLock().unlock();
			getSessionsLock().unlock();
		}
	}
	
	public void disconnect() {
		logger.info("disconnect server");
		if(server!=null) {
			server.close();
			server = null;
		}
	}

	@Override
	public void run() {
	}
	
	public Sessions getSessions() {
		return sessions;
	}
	
	/**
	 * @return global lock for sessions attribute
	 */
	public Lock getSessionsLock() {
		return sessionsLock;
	}
	
	public Crooms getCrooms() {
		return crooms;
	}
	
	/**
	 * WARNING: don't lock sessionslock after croomslock,
	 * because sessions is a subset of crooms context.<br/>
	 * SOLUTION: firstly use sessionslock and after croomslock
	 * @return global lock for crooms attribute
	 */
	public Lock getCroomsLock() {
		return croomsLock;
	}
	
	public static void main(String[] args) {
		@SuppressWarnings("unused")
		Logger logger = Tools.initLog4jRootLogger();
		new UChatServer().run();
	}
}
